package com.spzx.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.bean.BeanUtils;
import com.spzx.product.domain.ProductSku;
import com.spzx.product.domain.SkuStock;
import com.spzx.product.mapper.ProductSkuMapper;
import com.spzx.product.mapper.SkuStockMapper;
import com.spzx.product.query.SkuQuery;
import com.spzx.product.service.ProductSkuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.product.api.vo.ProductSkuVo;
import com.spzx.product.vo.SkuPriceVo;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 商品sku 服务实现类
 * </p>
 *
 * @author spzx
 * @since 2024-12-19
 */
@Service
@Slf4j
@Log4j2
public class ProductSkuServiceImpl extends ServiceImpl<ProductSkuMapper, ProductSku> implements ProductSkuService {


    @Autowired
    private SkuStockMapper skuStockMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取销量前20的商品
     *
     * @return
     */
    @Override
    public List<ProductSkuVo> getTopSale() {
        return baseMapper.getTopSale();
    }

    @Override
    public List<ProductSkuVo> selectProductSkuList(SkuQuery skuQuery) {
        return baseMapper.selectProductSkuList(skuQuery);
    }

    @Override
    public SkuPriceVo getSkuPrice(Long skuId) {
        LambdaQueryWrapper<ProductSku> queryWrapper =
                new LambdaQueryWrapper<ProductSku>()
                        .eq(ProductSku::getId, skuId)
                        .select(ProductSku::getSalePrice, ProductSku::getMarketPrice);
        ProductSku productSku = baseMapper.selectOne(queryWrapper);

    /* 可以直接查询  内存利用率低
        ProductSku productSku = baseMapper.selectById(skuId); */

        //封装数据到SkuPriceVo对象中
        SkuPriceVo skuPriceVo = new SkuPriceVo();
        skuPriceVo.setMarketPrice(productSku.getMarketPrice());
        skuPriceVo.setSalePrice(productSku.getSalePrice());
        skuPriceVo.setSkuId(skuId);
        return skuPriceVo;
    }

    /**
     * 根据商品ID获取规格值和SKU ID的映射
     *
     * @param productId
     * @return
     */
    @Override
    public Map<String, Long> getSkuSpecValueMap(Long productId) {
        LambdaQueryWrapper<ProductSku> queryWrapper =
                new LambdaQueryWrapper<ProductSku>()
                        .select(ProductSku::getId, ProductSku::getSkuSpec)
                        .eq(ProductSku::getProductId, productId);

        // 根据查询条件获取商品SKU列表
        List<ProductSku> productSkuList = baseMapper.selectList(queryWrapper);

/*        // 检查是否有商品SKU
        if (productSkuList.isEmpty()) {
            return null;
        }*/
        /*else {
            // 将查询结果转换为Map，键为规格值，值为对应的SKU ID
            // 使用新建的HashMap进行转换
            HashMap<String, Long> hashMap = new HashMap<>();
            for (ProductSku productSku : productSkuList) {
                hashMap.put(productSku.getSkuSpec(), productSku.getId());
            }
            return hashMap;
        }*/
        // 将查询结果转换为Map，键为规格值，值为对应的SKU ID
        // 使用Java 8的Stream API进行转换，这样可以更简洁地实现相同的功能
        return productSkuList.stream()
                .collect(Collectors.toMap(ProductSku::getSkuSpec, ProductSku::getId));
    }

    /**
     * 根据SKU ID获取商品SKU详情
     * 解决缓存击穿问题
     * 采用：分布式加锁
     * @param skuId
     * @return
     */
/*    @Override
    public ProductSkuVo getProductSkuVoById(Long skuId) {
        //业务代码  根据SKU ID查询商品SKU信息
        //todo 1. 先从redis缓存中查询数据
        String dataKey = "product:sku:" + skuId;
        ProductSkuVo productSkuVo = (ProductSkuVo) redisTemplate.opsForValue().get(dataKey);
        if (productSkuVo != null) {
            log.info("从缓存中获取数据");
            return productSkuVo;
        } else {
            //todo 2. 如果缓存中没有再去数据库查询
            try {
                LambdaQueryWrapper<ProductSku> queryWrapper =
                        new LambdaQueryWrapper<ProductSku>()
                                .eq(ProductSku::getId, skuId);
                ProductSku productSku = baseMapper.selectOne(queryWrapper);

                // 封装数据到ProductSkuVo对象中
                productSkuVo = new ProductSkuVo();
                BeanUtils.copyProperties(productSku, productSkuVo);

                //查询sku_stock表中销量(available_num)和库存(sale_num)封装set到productSkuVo中
                LambdaQueryWrapper<SkuStock> skuStockQuery =
                        new LambdaQueryWrapper<SkuStock>()
                                .eq(SkuStock::getSkuId, skuId)
                                .select(SkuStock::getAvailableNum, SkuStock::getSaleNum);
                SkuStock skuStock = skuStockMapper.selectOne(skuStockQuery);
                productSkuVo.setStockNum(skuStock.getAvailableNum());
                productSkuVo.setSaleNum(skuStock.getSaleNum());

                //将数据存入redis
                long ttl = 10 * 60L;//10分钟过期时间
                redisTemplate.opsForValue().set(dataKey, productSkuVo, ttl, TimeUnit.SECONDS);
                return productSkuVo;

            } catch (BeansException e) {
                throw new RuntimeException(e);
            } finally {
                log.info("查询商品SKU详情结束");
            }
        }
    }*/


    /**
     * 根据SKU ID获取商品SKU详情
     * Redis的Bitmap解决缓存穿透问题
     *
     * @param skuId
     * @return
     */
    @Override
    public ProductSkuVo getProductSkuVoById(Long skuId) {

        try {
            String key = "product:sku:data";
            Boolean isExist = redisTemplate.opsForValue().getBit(key, skuId);
            if (!isExist) {
                throw new ServiceException("商品不存在", 555);
            }

            //获取分布式锁
            String lockKey = "product:sku:lock" + skuId;
            String lockValue = UUID.randomUUID().toString().replace("-", "");
            //设置过期时间，防止死锁
            // timeout : 超时时间 5秒
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5L, TimeUnit.SECONDS);

            if (flag) {
                try {
                    //todo 1. 先从redis缓存中查询数据
                    String dataKey = "product:sku:" + skuId;
                    ProductSkuVo productSkuVo = (ProductSkuVo) redisTemplate.opsForValue().get(dataKey);
                    if (productSkuVo != null) {
                        log.warn("缓存中获取数据");
                        return productSkuVo;
                    }
                    //todo 2. 如果缓存中没有再去数据库查询
                    log.warn("缓存中无数据，从数据库查询");
                    productSkuVo = databaseQuery(skuId);

                    long ttl = productSkuVo == null ? 60L : 10 * 60L;//10分钟过期时间 如果为空，则设置60秒过期时间，防止缓存击穿问题

                    //将数据存入redis
                    redisTemplate.opsForValue().set(dataKey, productSkuVo, ttl, TimeUnit.SECONDS);

                    return productSkuVo;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    log.info("查询商品SKU详情结束");
                    //todo 释放锁 lua脚本
                    String script = """
                            if redis.call('get', KEYS[1]) == ARGV[1] then
                                return redis.call('del', KEYS[1])
                            else
                                return 0
                            end
                            """;
                    Long lock = (Long) redisTemplate.execute(
                            new DefaultRedisScript<>(script, Long.class), List.of(lockKey), lockValue);
                    //                            Arrays.asList(lockKey),
    /*                    DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>();
                        defaultRedisScript.setScriptText(script);
                        defaultRedisScript.setResultType(Boolean.class);
                        redisTemplate.execute(defaultRedisScript, List.of(lockKey), lockValue);*/
                    log.info("释放锁的结果为：{}", lock);
                }
            } else {
                try {
                    //等待一段时间后再次尝试 自旋
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //如果获取锁失败，则等待一段时间后再次尝试 自旋
                return this.getProductSkuVoById(skuId);
            }
        } catch (ServiceException se) {
            if (se.getCode().intValue() == 555) {
                throw se;
            } else {
                log.error("查询商品详情异常：{}", se.getMessage());
                return databaseQuery(skuId);
            }
        } catch (
                Exception e) {
            log.error("查询商品详情异常：{}", e.getMessage());
            log.info("兜底方案，直接从数据库查询商品详情");
            return databaseQuery(skuId);
        }
    }

    @Override
    public List<SkuPriceVo> getSkuPriceList(List<Long> skuIdList) {
        LambdaQueryWrapper<ProductSku> queryWrapper =
                new LambdaQueryWrapper<ProductSku>()
                        .in(ProductSku::getId, skuIdList)
                        .select(ProductSku::getId, ProductSku::getSalePrice);
        List<ProductSku> productSkuList = baseMapper.selectList(queryWrapper);

        if (!CollectionUtils.isEmpty(productSkuList)) {
            return productSkuList.stream().map(productSku -> {
                SkuPriceVo skuPriceVo = new SkuPriceVo();
                skuPriceVo.setSkuId(productSku.getId());
                skuPriceVo.setSalePrice(productSku.getSalePrice());
                return skuPriceVo;
            }).collect(Collectors.toList());
        } else {
            return null;
        }

    }


    /**
     * 数据库查询商品详情
     *
     * @param skuId
     * @return
     */
    private ProductSkuVo databaseQuery(Long skuId) {
        // 业务逻辑代码
        //兜底方案，直接从数据库查询
        LambdaQueryWrapper<ProductSku> queryWrapper =
                new LambdaQueryWrapper<ProductSku>()
                        .eq(ProductSku::getId, skuId);
        ProductSku productSku = baseMapper.selectOne(queryWrapper);

        // 封装数据到ProductSkuVo对象中
        ProductSkuVo productSkuVo = new ProductSkuVo();
        if (productSku != null) {
            BeanUtils.copyProperties(productSku, productSkuVo);

            //查询sku_stock表中销量(available_num)和库存(sale_num)封装set到productSkuVo中
            LambdaQueryWrapper<SkuStock> skuStockQuery =
                    new LambdaQueryWrapper<SkuStock>()
                            .eq(SkuStock::getSkuId, skuId)
                            .select(SkuStock::getAvailableNum, SkuStock::getSaleNum);
            SkuStock skuStock = skuStockMapper.selectOne(skuStockQuery);
            productSkuVo.setStockNum(skuStock.getAvailableNum());
            productSkuVo.setSaleNum(skuStock.getSaleNum());
            return productSkuVo;
        }
        return productSkuVo;
    }
}
